using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class BoxGeometry : BufferGeometry
    {
        public class Parameters
        {
            public double width;
            public double height;
            public double depth;
            public int widthSegments;
            public int heightSegments;
            public int depthSegments;
        }
        #region Properties

        public Parameters parameters;

        #endregion

        #region constructor
        public BoxGeometry(double width = 1, double height = 1, double depth = 1, int widthSegments = 1, int heightSegments = 1, int depthSegments = 1) : base()
        {
            this.type = "BoxGeometry";
            this.parameters = new Parameters
            {
                width = width,
                height = height,
                depth = depth,
                widthSegments = widthSegments,
                heightSegments = heightSegments,
                depthSegments = depthSegments
            };
            var scope = this;
            // segments
            //widthSegments = (int)Math.Floor(widthSegments);
            //heightSegments = (int)Math.Floor(heightSegments);
            //depthSegments = (int)Math.Floor(depthSegments);
            // buffers
            var indices = new ListEx<int>();
            var vertices = new ListEx<double>();
            var normals = new ListEx<double>();
            var uvs = new ListEx<double>();
            // helper variables
            var numberOfVertices = 0;
            var groupStart = 0;
            // build each side of the box geometry
            buildPlane("Z", "Y", "X", -1, -1, depth, height, width, depthSegments, heightSegments, 0); // px
            buildPlane("Z", "Y", "X", 1, -1, depth, height, -width, depthSegments, heightSegments, 1); // nx
            buildPlane("X", "Z", "Y", 1, 1, width, depth, height, widthSegments, depthSegments, 2); // py
            buildPlane("X", "Z", "Y", 1, -1, width, depth, -height, widthSegments, depthSegments, 3); // ny
            buildPlane("X", "Y", "Z", 1, -1, width, height, depth, widthSegments, heightSegments, 4); // pz
            buildPlane("X", "Y", "Z", -1, -1, width, height, -depth, widthSegments, heightSegments, 5); // nz
                                                                                                        // build geometry
            this.setIndex(indices.ToArray());
            this.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            this.setAttribute("normal", new Float32BufferAttribute(normals.ToArray(), 3));
            this.setAttribute("uv", new Float32BufferAttribute(uvs.ToArray(), 2));
            void buildPlane(string u, string v, string w, double udir, double vdir, double _width, double _height, double _depth, int gridX, double gridY, int materialIndex)
            {
                var segmentWidth = _width / gridX;
                var segmentHeight = _height / gridY;
                var widthHalf = _width / 2;
                var heightHalf = _height / 2;
                var depthHalf = _depth / 2;
                var gridX1 = gridX + 1;
                var gridY1 = gridY + 1;
                var vertexCounter = 0;
                var groupCount = 0;
                var vector = new Vector3();
                // generate vertices, normals and uvs
                for (int iy = 0; iy < gridY1; iy++)
                {
                    var y = iy * segmentHeight - heightHalf;
                    for (int ix = 0; ix < gridX1; ix++)
                    {
                        var x = ix * segmentWidth - widthHalf;
                        // set values to correct vector component
                        vector.SetField(u, x * udir);
                        vector.SetField(v, y * vdir);
                        vector.SetField(w, depthHalf);
                        // now apply vector to vertex buffer
                        vertices.Push(vector.X, vector.Y, vector.Z);
                        // set values to correct vector component
                        vector.SetField(u, 0);
                        vector.SetField(v, 0);
                        vector.SetField(w, _depth > 0 ? 1 : -1);
                        // now apply vector to normal buffer
                        normals.Push(vector.X, vector.Y, vector.Z);
                        // uvs
                        uvs.Push(ix / (double)gridX);
                        uvs.Push(1 - (iy / (double)gridY));
                        // counters
                        vertexCounter += 1;
                    }
                }
                // indices
                // 1. you need three indices to draw a single face
                // 2. a single segment consists of two faces
                // 3. so we need to generate six (2*3) indices per segment
                for (int iy = 0; iy < gridY; iy++)
                {
                    for (int ix = 0; ix < gridX; ix++)
                    {
                        var a = numberOfVertices + ix + gridX1 * iy;
                        var b = numberOfVertices + ix + gridX1 * (iy + 1);
                        var c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
                        var d = numberOfVertices + (ix + 1) + gridX1 * iy;
                        // faces
                        indices.Push(a, b, d);
                        indices.Push(b, c, d);
                        // increase counter
                        groupCount += 6;
                    }
                }
                // add a group to the geometry. this will ensure multi material support
                scope.addGroup(groupStart, groupCount, materialIndex);
                // calculate new start value for groups
                groupStart += groupCount;
                // update total number of vertices
                numberOfVertices += vertexCounter;
            }

        }

        #endregion

        #region methods
        public BoxGeometry copy(BoxGeometry source)
        {
            base.copy(source);
            this.parameters = new Parameters()
            {
                width = source.parameters.width,
                height = source.parameters.height,
                depth = source.parameters.depth,
                widthSegments = source.parameters.widthSegments,
                heightSegments = source.parameters.heightSegments,
                depthSegments = source.parameters.depthSegments
            };
            return this;

        }

        #endregion

    }
}
